package physics.movement;

import java.awt.geom.Point2D;
import model.objects.GameObject;
import model.objects.Level;
import physics.EuclideanVector;


/**
 * Defines the movement of the hero in the platformer game.
 * 
 * @author geo4
 * 
 */
public class HeroMovementStrategy implements MovementStrategy {

    private static final double MAXIMUM_SPEED = 10;

    /**
     * The value that the y-component of the GameObject's velocity will be set
     * to if jump method is called.
     */
    private static final double JUMP_SPEED = -5;
    /**
     * The value of the GameObject's lateral acceleration if instructed to
     * moveLeft or moveRight.
     */
    private static final int RUN_ACCELERATION = 2;

    @Override
    public void updatePosition (Level level, GameObject mover, double dt,
                                EuclideanVector ... globalForces) {

        Point2D accelWithForces =
                combineAccelerationAndForces(mover.getAcceleration(), globalForces);

        Point2D velocity = mover.getVelocity();
        velocity.setLocation(velocity.getX() + accelWithForces.getX(), velocity.getY() +
                                                                       accelWithForces.getY());
        if (velocity.getX() > MAXIMUM_SPEED) {
            velocity.setLocation(MAXIMUM_SPEED, velocity.getY());
        }
        if (velocity.getX() < -MAXIMUM_SPEED) {
            velocity.setLocation(-MAXIMUM_SPEED, velocity.getY());
        }
        mover.setPosition(mover.getBounds().getX() + velocity.getX(), mover.getBounds().getY() +
                                                                      velocity.getY());

        if (checkIfOutOfLevel(level, mover)) {
            level.setLevelLost(true, "Hero was out of level bounds");
        }
    }

    private Point2D combineAccelerationAndForces (Point2D acceleration, EuclideanVector ... forces) {
        Point2D accelWithForces = new Point2D.Double();
        EuclideanVector totalGlobalForce = new EuclideanVector();
        for (EuclideanVector force : forces) {
            totalGlobalForce = totalGlobalForce.add(force);
        }
        accelWithForces.setLocation(acceleration.getX() + totalGlobalForce.getX(),
                                    acceleration.getY() + totalGlobalForce.getY());
        return accelWithForces;
    }

    private boolean checkIfOutOfLevel (Level level, GameObject mover) {
        return mover.getBounds().getMinX() < 0 || mover.getBounds().getMaxX() > level.getWidth() ||
               mover.getBounds().getMinY() < 0 || mover.getBounds().getMaxY() > level.getHeight();
    }

    /**
     * Called to instruct the hero to jump if it is on the ground.
     * 
     * @param hero hero that is jumping
     */
    public void jumpOn (GameObject hero) {
        if (hero.isOnGround()) {
            hero.getVelocity().setLocation(hero.getVelocity().getX(), JUMP_SPEED);
        }
    }

    /**
     * Called to instruct the hero that the jump key has been released.
     * 
     * @param hero hero that should no longer be jumping
     */
    public void jumpOff (GameObject hero) {
        /*
         * Null behavior
         */
    }

    /**
     * Called to instruct the hero to run left.
     * 
     * @param hero hero that should run left
     */
    public void runLeft (GameObject hero) {
        hero.getAcceleration().setLocation(-RUN_ACCELERATION, hero.getAcceleration().getY());
    }

    /**
     * Called to instruct the hero to run right.
     * 
     * @param hero hero that should run right
     */
    public void runRight (GameObject hero) {
        hero.getAcceleration().setLocation(RUN_ACCELERATION, hero.getAcceleration().getY());
    }

    /**
     * Called to instruct the hero that the run key has been released.
     * 
     * @param hero hero that should stop running
     */
    public void stopRunning (GameObject hero) {
        hero.getVelocity().setLocation(0, hero.getVelocity().getY());
    }
}
